package com.fwtai.security;

import com.fwtai.bean.JwtUser;
import com.fwtai.config.ConfigFile;
import com.fwtai.config.FlagToken;
import com.fwtai.config.LocalLoginUser;
import com.fwtai.config.LocalUserId;
import com.fwtai.config.RenewalToken;
import com.fwtai.exception.ErrorException;
import com.fwtai.tool.ToolBean;
import com.fwtai.tool.ToolClient;
import com.fwtai.tool.ToolJWT;
import com.fwtai.tool.ToolString;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import org.mybatis.spring.MyBatisSystemException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.CompletionException;

/**
 * 鉴权操作
*/
public final class AuthorizationFilter extends BasicAuthenticationFilter{

  public AuthorizationFilter(final AuthenticationManager authenticationManager){
    super(authenticationManager);
  }

  @Override
  protected void doFilterInternal(final HttpServletRequest request,final HttpServletResponse response,final FilterChain chain) throws IOException, ServletException{
    final String uri = request.getRequestURI();
    System.out.println(uri);
    final String refresh_token = ToolString.wipeString(request.getHeader(ConfigFile.REFRESH_TOKEN));
    final String access_token = ToolString.wipeString(request.getHeader(ConfigFile.ACCESS_TOKEN));
    final String url_refresh_token = ToolString.wipeString(request.getParameter(ConfigFile.REFRESH_TOKEN));
    final String url_access_token = ToolString.wipeString(request.getParameter(ConfigFile.ACCESS_TOKEN));
    final String refresh = refresh_token == null ? url_refresh_token : refresh_token;
    final String access = access_token == null ? url_access_token : access_token;
    if(uri.contains(ConfigFile.guest_api)){//token选填,若还有其他版本的此处无需修改
      if(access != null){
        try {
          ToolJWT.parser(access);
        } catch (final Exception e) {
          if(e instanceof ExpiredJwtException || e instanceof MalformedJwtException){
            ToolClient.responseJson(ToolClient.tokenInvalid(),response);
            return;
          }
        }
        //通过令牌获取用户名称
        final String userId = ToolJWT.extractUserId(access);
        // 根据userId 从 redis ，获取用户 authentication 角色权限信息
        //判断用户不为空，且SecurityContextHolder授权信息还是空的
        LocalUserId.set(userId);
        try {
          //通过用户信息得到UserDetails
          /*final JwtUser jwtUser = ToolBean.getBean(request,UserDetailsServiceImpl.class).getUserInfo(userId);//todo 仅需登录不需要鉴权,若有些特定的接口需要鉴权的可以在此处理!!!
          SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(jwtUser.getUsername(),null,jwtUser.getAuthorities()));
          super.doFilterInternal(request,response,chain);//小程序端无需鉴权*/
          //api接口无需鉴权,仅需验证是否是有效的token即可
          SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(userId,null,new ArrayList<>(0)));
          chain.doFilter(request,response);
        } catch (final Exception exception) {
          if(exception instanceof MyBatisSystemException){
            ToolClient.responseJson(ToolClient.exceptionJson("服务出现错误,稍候重试"),response);
          }else if(exception instanceof ErrorException){
            ToolClient.responseJson(exception.getMessage(),response);
          }else if(exception instanceof CompletionException){
            ToolClient.responseJson(ToolClient.exceptionJson(),response);
          }
        }
      }else{
        SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("guest",null,new ArrayList<>(0)));//模拟已登录的账号,因为移动端api不鉴权,所以给的空的权限!!!
        chain.doFilter(request,response);//todo 未携带鉴权信息的,ok
      }
    }else{
      if(access != null){//删除一个条件判断,if(refresh != null && access != null) 移动端token失效时重新登录即可!!!,因为移动端只有 accessToken
        //判断令牌是否过期，默认是一周,比较好的解决方案是：登录成功获得token后，将token存储到数据库（redis）
        try {
          ToolJWT.parser(refresh);//适用于PC的后端的更换token
        } catch (final Exception e) {
          if(e instanceof ExpiredJwtException){
            RenewalToken.set(access);
          }
        }
        try {
          //通过令牌获取用户名称
          final String userId = ToolJWT.extractUserId(access);
          // todo 根据userId 从 redis ，获取用户 authentication 角色权限信息
          //判断用户不为空，且SecurityContextHolder授权信息还是空的
          LocalUserId.set(userId);
          final String loginUser = request.getHeader(ConfigFile.currentLoginUser);
          if(loginUser != null){
            LocalLoginUser.set(loginUser);
          }
          final boolean match = Arrays.asList(ConfigFile.IGNORE_URLS).contains(uri);//忽略的url不查询数据库,要么改为正则表达式
          if(uri.startsWith("/api/v") || match){//若是api小程序端无需鉴权,仅需验证是否是有效的token即可
            SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(userId,null,new ArrayList<>(0)));
          }else{
            final String[] split = uri.split("/");
            //通过用户信息得到UserDetails
            final JwtUser jwtUser = ToolBean.getBean(request,UserDetailsServiceImpl.class).getUserById(userId,(split.length > 3) ? extractUri(uri) : uri.substring(1));
            SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(jwtUser.getUsername(),null,jwtUser.getAuthorities()));//此处的 jwtUser.getUsername()是userId,若是用到user_name登录账号的可用LocalLoginUser.get()获取
          }
          super.doFilterInternal(request,response,chain);
        } catch (final Exception exception){
          if(exception instanceof ExpiredJwtException){//说明access也过期了,需要重新登录
            RenewalToken.remove();
            ToolClient.responseJson(ToolClient.tokenInvalid(),response);
            return;
          }
          final boolean bl = uri.endsWith("/listData");
          if(exception instanceof MyBatisSystemException){
            final String json = bl ? ToolClient.dataTableException() : ToolClient.exceptionJson();
            ToolClient.responseJson(json,response);
            return;
          }else if(exception instanceof ErrorException){
            final String json = bl ? ToolClient.dataTableException(exception.getMessage()) : exception.getMessage();
            ToolClient.responseJson(json,response);
            return;
          }else if(exception instanceof CompletionException){
            ToolClient.responseJson(ToolClient.exceptionJson(),response);
            return;
          }else{
            RenewalToken.remove();
            FlagToken.set(2);
          }
          //todo 勿删,走这里因为没有角色权限信息,所以要被 AuthenticationEntryPoint 的实现类拦截下来并执行
          chain.doFilter(request,response);//报错是否因为这个???,若是不加的话，则请求没有返回值
        }
      }else{
        chain.doFilter(request,response);
      }
    }
  }

  //处理restful风格的url
  private String extractUri(final String uri){
    final String url = uri.substring(1);
    final String slash = "/";
    final int location = url.indexOf(slash);
    final int pre = location + 1;
    return url.substring(0,pre + url.substring(pre).indexOf(slash));
  }
}